import serial
import time
import sys
from pathlib import Path
import json
from ..utils.config import SYSTEM_STATE_FILE

from ..utils.simple_logging import get_hardware_logger

# 获取硬件日志记录器
logger = get_hardware_logger("pump")

class PumpController:

    # 初始化串口
    def __init__(self, port=None, baudrate=9600, timeout=1):
        if port is None:
            logger.warning("未找到串口设备，将在离线模式运行")
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self.ser = None
        self.reagent = {
            "chamber": 1,
            "air": 2
        }

        self.tubevol = 2150

    # 修改某个.json文件某个键对应的值为..
    def modify_json_file(self, file_path, key, new_value):
        with open(file_path, 'r') as file:
            data = json.load(file)
        data[key] = new_value
        with open(file_path, 'w') as file:
            json.dump(data, file, indent=4)

    def get_speed(self, pump_id=1):
        """读取泵速度"""
        try:
            reply = self.send_command(f"/{pump_id}?2R")
            return int(reply[8:-6][1::2])
        except Exception as e:
            logger.error(f"读取泵速度失败: {e}")
            return None



    # 打开串口连接
    def open(self):
        try:
            self.ser = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                timeout=self.timeout
            )
            logger.info(f"串口已连接: {self.port}")
            return True
        except serial.SerialException as e:
            logger.error(f"串口连接失败: {e}")
            return False

    # 关闭串口连接
    def close(self):
        if self.ser and self.ser.is_open:
            self.ser.close()
            logger.info("串口已关闭")

    def send_command(self, command) -> bytes:
        """发送命令并读取响应"""
        for i in range(3):
            # 检查串口连接
            if not self.ser or not self.ser.is_open:
                logger.warning("尝试重新连接串口...")
                if not self.open():
                    logger.error("串口重连失败")
                    return b""
            # 发送命令
            full_command = f"{command}\r\n".encode("ascii")
            self.ser.write(full_command)
            try:
                response = self.ser.read_until(b"\r\n").hex()
                logger.info(f"第{i + 1}次响应-----{command}:{response}")
                return response
            except TimeoutError:
                logger.info(f"第 {i + 1} 次响应:超时")
        logger.error(f"命令 {command} 重试 {i + 1} 次后失败")
        sys.exit()

    # 查询泵状态
    def query_pump_status(self, pump_id=1):
        command = f"/{pump_id}QR"
        response = self.send_command(command)
        try:
            status_byte = response[6:8]
            if status_byte == "60":
                
                return True
            elif status_byte == "40":
                return False
            else:
                logger.error(f"未知的状态字节：{response}")
                return None
        except Exception as e:
            logger.error(f"解析响应时出错：{e}")

    # 查询泵所在端口
    def get_current_port(self, pump_id=1):
        for _ in range(5):
            response = self.send_command(f"/{pump_id}?6R")
            status_byte = response[9:10]
            if status_byte.isdigit():
                return int(status_byte)
            time.sleep(1)
        logger.error("查询端口超时")
        return None

    # 查询活塞位置
    def query_pump_position(self, pump_id=1) -> int:
        """查询活塞位置（基于字节流解析）"""
        command = f"/{pump_id}?R"
        response = self.send_command(command)
        try:
            # 假设响应格式: b'/1?A600\r\n'，其中 600 是位置
            # 提取数字部分（从第5字节开始到倒数3字节）
            position_str = response[8:-6][1::2]  # 示例: b'A600' -> '600'
            logger.info(f"活塞位置: {position_str}")
            return int(position_str)
        except (IndexError, ValueError, UnicodeDecodeError) as e:
            logger.error(f"解析活塞位置失败: {e}")
            return None

    # 设置泵速度
    def set_speed(self, pump_id=1, speed=500):
        command = f"/{pump_id}V{speed}R"
        reply = self.send_command(command)
        if reply:
            nowspeed = self.send_command(f"/{pump_id}?2R")
            nowspeed = int(nowspeed[8:-6][1::2])
            logger.info(f"{pump_id} 号泵速度: {nowspeed}")
            return nowspeed
        logger.error("设置泵速度失败")

    # 等待泵空闲（带超时）
    def _wait_pump_idle(self, pump_id=1, timeout: int = 60) -> bool:
        """等待泵空闲（带超时）"""
        start_time = time.time()
        while time.time() - start_time < timeout:
            status = self.query_pump_status(pump_id)
            if status:
                return True
            time.sleep(0.5)
        return False

    # 重置泵
    def reset(self, pump_id=1):
        command = f"/{pump_id}ZR"
        if self._wait_pump_idle:
            self.send_command(command)
        # 等待泵空闲
        if self._wait_pump_idle(pump_id):
            self.modify_json_file(file_path=SYSTEM_STATE_FILE, key="device_status", new_value="idle")
            return True
        logger.error("重置泵失败：超时，泵忙")

    # 移动活塞到0
    def move_pump_to_zero(self, pump_id=1):
        command = f"/{pump_id}A0R"
        # 活塞位置为0时返回true，不为0时发送命令移动活塞到0，等待活塞到位
        if self.query_pump_position(pump_id) == 0:
            logger.info("活塞已在0位置")
            return True
        # 查询活塞位置，等待活塞到位
        if self._wait_pump_idle:
            self.send_command(command)
            for i in range(60):
                if self.query_pump_position(pump_id) == 0:
                    logger.info("活塞已移动到0位置")
                    return True
                else:
                    time.sleep(1)
            logger.error("活塞移动到0位置超时")
            return False

    # 挤液
    def dispense(self, pump_id=1, volume: int = 1000, tolerance: int = 5) -> bool:
        """挤液操作（带容差、超时和卡死检测）"""
        # 1. 参数校验
        if volume <= 0 or not isinstance(volume, int):
            logger.error(f"泵 {pump_id} 挤液失败：无效体积 {volume}（必须为正整数）")
            return False

        # 2. 等待泵空闲（带超时）
        if not self._wait_pump_idle(pump_id, timeout=30):
            logger.error(f"泵 {pump_id} 抽液失败：等待空闲超时")
            return False
        # 3. 获取初始位置并发送命令（原子操作）
        try:
            initial_position = self.query_pump_position(pump_id)
            if not self.query_pump_status():
                logger.error(f"泵 {pump_id} 挤液失败：无法获取初始位置")
                return False
            command = f"/{pump_id}D{volume}R"
            self.send_command(command)
        except Exception as e:
            logger.error(f"泵 {pump_id} 初始化挤液操作异常: {e}")
            return False

        # 4. 等待目标位置到达（带容差和超时）
        target_position = initial_position - volume
        start_time = time.time()
        timeout = 60  # 秒
        last_position = initial_position
        stagnation_count = 0  # 卡死检测计数器
        while (time.time() - start_time) < timeout:
            current_position = self.query_pump_position(pump_id)
            # 4.1 处理无效位置响应
            if current_position is None:
                logger.warning(f"泵 {pump_id} 位置查询失败，重试中...")
                time.sleep(1)
                continue
            # 4.2 卡死检测
            if current_position == last_position:
                stagnation_count += 1
                if stagnation_count >= 6:  # 连续3秒无变化
                    logger.warning(f"泵 {pump_id} 可能卡死，尝试重置...")
                    return False
            else:
                stagnation_count = 0
                last_position = current_position

            # 4.3 容差判断（允许误差）
            if abs(current_position - target_position) <= tolerance:
                logger.info(f"泵 {pump_id} 挤液完成（目标: {target_position}, 实际: {current_position}）")
                return True
            # 4.4 进度日志（每2秒记录一次）
            if int(time.time() - start_time) % 2 == 0:
                logger.info(f"泵 {pump_id} 挤液进度: {initial_position - current_position}/{volume}")

            time.sleep(0.5)
        # 5. 超时处理
        final_position = self.query_pump_position(pump_id)
        logger.error(
            f"泵 {pump_id} 挤液超时，目标: {target_position}, "
            f"初始: {initial_position}, 最终: {final_position}"
        )
        return False

    # 执行抽液操作，支持容差和超时控制
    def aspirate(self, pump_id=1, volume=1000, tolerance=5) -> bool:
        # 1. 参数校验
        if volume <= 0:
            logger.error(f"泵 {pump_id} 抽液失败：无效体积 {volume}（必须为正数）")
            return False
        # 2. 等待泵空闲（带超时）
        if not self._wait_pump_idle(pump_id, timeout=30):
            logger.error(f"泵 {pump_id} 抽液失败：等待空闲超时")
            return False
        # 3. 获取初始位置并发送命令（原子操作）
        try:
            initial_position = self.query_pump_position(pump_id)
            if not self.query_pump_status():
                logger.error(f"泵 {pump_id} 抽液失败：无法获取初始位置")
                return False
            command = f"/{pump_id}P{volume}R"
            self.send_command(command)
        except Exception as e:
            logger.error(f"泵 {pump_id} 初始化抽液操作异常: {e}")
            return False
        # 4. 等待目标位置到达（带容差和超时）
        target_position = initial_position + volume
        start_time = time.time()
        timeout = 60  # 秒
        last_position = initial_position
        stagnation_count = 0  # 卡死检测计数器
        while (time.time() - start_time) < timeout:
            current_position = self.query_pump_position(pump_id)
            # 4.1 处理无效位置响应
            if current_position is None:
                logger.warning(f"泵 {pump_id} 位置查询失败，重试中...")
                time.sleep(1)
                continue
            # 4.2 进度跟踪与卡死检测
            if current_position == last_position:
                stagnation_count += 1
                if stagnation_count >= 6:  # 连续3秒无变化
                    logger.warning(f"泵 {pump_id} 可能卡死，尝试重置...")
                    return False
            else:
                stagnation_count = 0
                last_position = current_position
            # 4.3 容差判断
            if abs(current_position - target_position) <= tolerance:
                logger.info(f"泵 {pump_id} 抽液完成（目标: {target_position}, 实际: {current_position}）")
                return True
            # 4.4 进度日志（每2秒记录一次）
            if int(time.time() - start_time) % 2 == 0:
                logger.info(f"泵 {pump_id} 抽液进度: {current_position - initial_position}/{volume}")
            time.sleep(0.5)
        # 5. 超时处理
        final_position = self.query_pump_position(pump_id)
        logger.error(
            f"泵 {pump_id} 抽液超时，目标: {target_position}, "
            f"初始: {initial_position}, 最终: {final_position}"
        )
        return False

    # 旋转至指定端口
    def rotate_to_port(self, pump_id=1, port_number=1):
        try:
            current_port = self.get_current_port(pump_id=pump_id)
            if current_port == port_number:
                logger.info(f"泵 {pump_id} 已在端口 {port_number}")
                return True
            if self._wait_pump_idle(pump_id=pump_id):
                delta = (port_number - current_port) % 9
                command = f"/{pump_id}{'I' if delta <= 4 else 'O'}{port_number}R"
                self.send_command(command)
            start = time.time()
            while time.time() - start < 120:
                if self.get_current_port(pump_id=pump_id) == port_number:
                    logger.info(f"泵 {pump_id} 已到达端口 {port_number}")
                    return True
                time.sleep(1)
            logger.error(f"泵 {pump_id} 旋转到端口 {port_number} 超时")
            return False
        except Exception as e:
            logger.error(f"旋转异常: {e}")
            return False


    def move_and_aspirate(self, volume, port_number, pump_id=1):
        self.rotate_to_port(pump_id=pump_id, port_number=port_number)
        self.aspirate(pump_id=pump_id, volume=volume)

    def move_and_dispense(self, volume, port_number, pump_id=1):
        self.rotate_to_port(pump_id=pump_id, port_number=port_number)
        self.dispense(pump_id=pump_id, volume=volume)

    def move_to_chamber_and_dispense_to_zero(self, pump_id=1):
        self.rotate_to_port(pump_id=pump_id, port_number=self.reagent["chamber"])
        self.move_pump_to_zero(pump_id=pump_id)

    @staticmethod
    def wait_with_logging(total_wait_time):
        """
        等待并每隔 10 秒记录剩余时间
        :param total_wait_time: 总等待时间（秒）
        """
        remaining_time = total_wait_time  # 剩余时间
        interval = 10  # 记录间隔时间（秒）
        logger.info(f"开始等待 {total_wait_time} 秒")
        while remaining_time > 0:
            if remaining_time >= interval:
                # 等待 10 秒
                time.sleep(interval)
                remaining_time -= interval
            else:
                # 等待剩余时间
                time.sleep(remaining_time)
                remaining_time = 0
            # 记录剩余时间到日志
            logger.info(f"剩余等待时间: {remaining_time} 秒")
        logger.info("等待完成")
